#### 使用data_downloader.py下载保存好的数据，建立交易回测系统
import os
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import logging
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.font_manager import FontProperties
import time
from tqdm import tqdm
from sqlalchemy import create_engine

class Config:
    """回测系统配置"""
    def __init__(self):
        # 数据路径配置
        self.data_root = 'full_stock_data'  # 数据根目录
        self.daily_path = os.path.join(self.data_root, 'daily')    # 日线数据目录
        self.basic_path = os.path.join(self.data_root, 'basic')    # 基本面数据目录
        self.index_path = os.path.join(self.data_root, 'index')    # 指数数据目录
        
        # 输出路径配置
        self.output_root = 'output'                                # 输出根目录
        self.result_path = os.path.join(self.output_root, 'results')  # 结果输出目录
        self.log_path = os.path.join(self.output_root, 'logs')       # 日志输出目录
        
        # 创建必要的目录
        for path in [self.data_root, self.daily_path, self.basic_path, 
                    self.index_path, self.output_root, self.result_path, 
                    self.log_path]:
            if not os.path.exists(path):
                os.makedirs(path)
                
        # 回测区间
        self.start_date = '20230101'  # 回测起始日期
        self.end_date = '20250101'    # 回测结束日期
        
        # 资金相关
        self.initial_capital = 100000  # 初始资金
        
        # 持仓相关
        self.max_positions = 8         # 最大持仓数量
        
        # 交易条件
        self.stop_loss_pct = -0.05    # 止损线
        self.stop_profit_pct = 0.20   # 止盈线
        self.trailing_stop_pct = 0.05  # 追踪止损比例
        self.max_hold_days = 240       # 最大持仓天数
        
        # 选股条件
        self.min_price = 5.0          # 最小股价
        self.max_price = 100.0        # 最大股价
        self.vol_ratio_threshold = 1.0 # 成交量放大倍数要求
        self.max_turnover_rate_f = 15.0 # 最大换手率(%), 用于选股
        

        # 技术指标参数
        self.ma_short = 5             # 短期均线
        self.ma_medium = 10           # 中期均线
        self.ma_long = 20             # 长期均线
        self.rsi_period = 14          # RSI周期
        self.macd_fast = 12           # MACD快线
        self.macd_slow = 26           # MACD慢线
        self.macd_signal = 9          # MACD信号线
        
        # 指数相关
        self.benchmark_code = '000001.SH'  # 基准指数代码
        self.market_codes = ['000001.SH', '399001.SZ']  # 主要市场指数
        
        # 数据加载配置
        self.max_stock_count = 1000  # 最大加载的股票数量，设置为None则加载所有股票
        
        # 动态仓位管理开关和参数
        self.enable_dynamic_position = False    # 是否启用动态仓位管理
        self.position_reduce_threshold = 0.02  # 触发减仓的账户回撤阈值（2%）
        self.position_increase_threshold = 0.01  # 触发加仓的账户上涨阈值（1%）
        self.position_reduce_step = 0.02      # 减仓步长（2%）
        self.position_increase_step = 0.01    # 加仓步长（1%）
        self.max_position_pct = 0.8 / self.max_positions          # 单个持仓最大仓位（10%), 8只股票的情况
        self.min_position_pct = self.max_position_pct / 5.0       # 单个持仓最小仓位（2%), 8只股票的情况
        self.last_high_value = None           # 记录历史最高净值

        self.last_low_value = None            # 记录触发减仓的净值

class DataLoader:
    """数据加载器"""
    def __init__(self, config):
        self.config = config
        self.logger = logging.getLogger(__name__)
        
    def load_stock_data(self, ts_code):
        """加载单个股票的所有数据"""
        try:
            # 加载日线数据
            daily_path = os.path.join(self.config.data_root, 'daily')
            daily_file = os.path.join(daily_path, f"{ts_code}.csv")
            
            if not os.path.exists(daily_file):
                self.logger.debug(f"找不到股票日线数据: {daily_file}")
                return None
                
            daily_data = pd.read_csv(daily_file)
            daily_data['trade_date'] = pd.to_datetime(daily_data['trade_date'].astype(str))
            
            # 确保所有成交量均线列都转换为相同的数据类型
            vol_ma_cols = ['vol_ma5', 'vol_ma10', 'vol_ma20', 'vol_ma30', 
                          'vol_ma40', 'vol_ma50', 'vol_ma60']
            for col in vol_ma_cols:
                if col in daily_data.columns:
                    daily_data[col] = daily_data[col].astype(float)
            
            # 选择需要的日线数据列
            daily_cols = ['trade_date', 'open', 'high', 'low', 'close', 'vol', 'amount',
                         'ma5', 'ma10', 'ma20', 'ma30', 'ma60', 'ma120', 'ma240',
                         'vol_ma5', 'vol_ma10', 'vol_ma20', 'vol_ma30', 'vol_ma40', 'vol_ma50', 'vol_ma60']
            daily_data = daily_data[daily_cols]
            
            # 加载基本面数据
            basic_path = os.path.join(self.config.data_root, 'basic')
            basic_file = os.path.join(basic_path, f"{ts_code}_basic.csv")
            
            if not os.path.exists(basic_file):
                self.logger.debug(f"找不到股票基本面数据: {basic_file}")
                return None
                
            basic_data = pd.read_csv(basic_file)
            basic_data['trade_date'] = pd.to_datetime(basic_data['trade_date'].astype(str))
            
            # 选择需要的基本面数据列
            basic_cols = ['trade_date', 'turnover_rate', 'volume_ratio', 
                         'pe_ttm', 'pb', 'total_mv', 'circ_mv']
            basic_data = basic_data[basic_cols]
            
            # 合并日线和基本面数据
            merged_data = pd.merge(daily_data, basic_data, on='trade_date', how='inner')
            merged_data.set_index('trade_date', inplace=True)
            merged_data.sort_index(inplace=True)
            
            # 筛选时间范围
            mask = (merged_data.index >= pd.to_datetime(self.config.start_date)) & \
                   (merged_data.index <= pd.to_datetime(self.config.end_date))
            filtered_data = merged_data[mask]
            
            if filtered_data.empty:
                self.logger.debug(f"股票 {ts_code} 在指定时间范围内没有数据")
                return None
                
            # 计算其他技术指标
            filtered_data = TechnicalAnalysis.calculate_indicators(filtered_data, self.config)
            
            return filtered_data
            
        except Exception as e:
            self.logger.debug(f"加载股票 {ts_code} 数据失败: {str(e)}")
            return None

    def load_stock_list(self):
        """加载股票列表"""
        daily_path = os.path.join(self.config.data_root, 'daily')
        if not os.path.exists(daily_path):
            raise FileNotFoundError(f"日线数据目录不存在: {daily_path}")
            
        stock_files = [f for f in os.listdir(daily_path) if f.endswith('.csv')]
        if not stock_files:
            raise ValueError(f"在 {daily_path} 目录中没有找到CSV文件")
        
        # 对股票文件列表进行排序，确保顺序一致
        stock_files.sort()
        
        # 如果设置了最大股票数量限制，选择前N只股票
        if self.config.max_stock_count is not None:
            if len(stock_files) > self.config.max_stock_count:
                stock_files = stock_files[:self.config.max_stock_count]
                self.logger.info(f"选择前 {self.config.max_stock_count} 只股票进行回测")
        
        # 检查文件是否真实存在
        valid_stock_codes = []
        with tqdm(total=len(stock_files), desc="检查股票数据", ncols=100) as pbar:
            for file in stock_files:
                ts_code = file.replace('.csv', '')
                daily_file = os.path.join(daily_path, file)
                basic_file = os.path.join(self.config.data_root, 'basic', f"{ts_code}_basic.csv")
                
                # 只添加同时有日线和基本面数据的股票
                if os.path.exists(daily_file) and os.path.exists(basic_file):
                    valid_stock_codes.append(ts_code)
                pbar.update(1)
        
        self.logger.info(f"找到 {len(valid_stock_codes)} 只有效股票数据")
        return pd.DataFrame({'ts_code': valid_stock_codes, 'name': valid_stock_codes})
        
    def load_index_data(self):
        """加载指数数据"""
        try:
            index_path = os.path.join(self.config.data_root, 'index')
            if not os.path.exists(index_path):
                self.logger.warning("指数数据目录不存在")
                return {}
                
            index_files = [f for f in os.listdir(index_path) if f.endswith('.csv')]
            if not index_files:
                self.logger.warning("没有找到指数数据文件")
                return {}
            
            index_data = {}
            for file in index_files:
                index_code = file.replace('.csv', '')
                file_path = os.path.join(index_path, file)
                
                try:
                    # 读取指数数据
                    df = pd.read_csv(file_path)
                    df['trade_date'] = pd.to_datetime(df['trade_date'].astype(str))
                    
                    # 选择需要的列
                    index_cols = ['trade_date', 'open', 'high', 'low', 'close', 
                                'vol', 'amount', 
                                'ma5', 'ma10', 'ma20', 'ma30', 'ma60', 'ma120', 'ma240',
                                'vol_ma5', 'vol_ma10', 'vol_ma20', 'vol_ma30', 'vol_ma40', 'vol_ma50', 'vol_ma60', 'vol_ma120', 'vol_ma240']
                    df = df[index_cols]
                    

                    # 设置日期索引
                    df.set_index('trade_date', inplace=True)
                    df.sort_index(inplace=True)
                    
                    # 筛选时间范围
                    mask = (df.index >= pd.to_datetime(self.config.start_date)) & \
                           (df.index <= pd.to_datetime(self.config.end_date))
                    filtered_df = df[mask]
                    
                    if not filtered_df.empty:
                        index_data[index_code] = filtered_df
                        self.logger.debug(f"成功加载指数 {index_code} 数据")
                    
                except Exception as e:
                    self.logger.warning(f"加载指数 {index_code} 数据失败: {str(e)}")
                    continue
                
            if not index_data:
                self.logger.warning("没有加载到任何有效的指数数据")
            else:
                self.logger.info(f"成功加载 {len(index_data)} 个指数数据")
                
            return index_data
            
        except Exception as e:
            self.logger.error(f"加载指数数据失败: {str(e)}")
            return {}

class TechnicalAnalysis:
    """技术分析工具"""
    @staticmethod
    def calculate_indicators(df, config):
        """计算技术指标"""
        try:
            # MACD (已有MA数据，不需要重复计算)
            exp1 = df['close'].ewm(span=config.macd_fast, adjust=False).mean()
            exp2 = df['close'].ewm(span=config.macd_slow, adjust=False).mean()
            df['macd'] = exp1 - exp2
            df['signal'] = df['macd'].ewm(span=config.macd_signal, adjust=False).mean()
            df['hist'] = df['macd'] - df['signal']
            
            # RSI
            delta = df['close'].diff()
            gain = (delta.where(delta > 0, 0)).rolling(window=config.rsi_period).mean()
            loss = (-delta.where(delta < 0, 0)).rolling(window=config.rsi_period).mean()
            rs = gain / loss
            df['rsi'] = 100 - (100 / (1 + rs))
            
            return df
            
        except Exception as e:
            logging.getLogger(__name__).error(f"计算技术指标失败: {str(e)}")
            return df

class ResultAnalyzer:
    """回测结果分析器"""
    def __init__(self, config):
        self.config = config
        self.logger = logging.getLogger(__name__)
        
        self.ensure_chinese_font()
        
    def ensure_chinese_font(self):
        '''
        sudo apt-get install fonts-wqy-zenhei fonts-wqy-microhei fonts-noto-cjk
        sudo apt install fonts-noto-cjk fonts-wqy-zenhei
        rm -rf ~/.cache/matplotlib
        '''
        
        """确保中文字体设置正确"""
        # 设置matplotlib的基本字体配置
        plt.rcParams['font.family'] = ['sans-serif']
        
        # 设置中文字体优先级
        plt.rcParams['font.sans-serif'] = ['Noto Sans CJK JP', 'WenQuanYi Zen Hei']
        
        # 解决负号显示问题
        plt.rcParams['axes.unicode_minus'] = False
        
        # 设置全局字体大小
        plt.rcParams['font.size'] = 12

    def analyze_and_plot(self, daily_dates, daily_values, daily_position_counts, title=None):
        """分析结果并绘制收益曲线"""
        try:
            # 创建图形
            plt.figure(figsize=(12, 6))
            
            # 计算收益和最大回撤
            initial_value = daily_values[0]
            final_value = daily_values[-1]
            total_return = (final_value / initial_value - 1) * 100
            
            # 计算最大回撤
            max_value = daily_values[0]
            max_drawdown = 0
            for value in daily_values:
                max_value = max(max_value, value)
                drawdown = (max_value - value) / max_value * 100
                max_drawdown = max(max_drawdown, drawdown)
            
            # 记录分析结果
            self.logger.info("=" * 50)
            self.logger.info("回测结果摘要:")
            self.logger.info("-" * 50)
            self.logger.info(f"初始资金: {initial_value:,.2f}")
            self.logger.info(f"最终资金: {final_value:,.2f}")
            self.logger.info(f"总收益率: {total_return:+.2f}%")
            self.logger.info(f"最大回撤: {max_drawdown:.2f}%")
            self.logger.info("-" * 50)
            
            # 绘制收益曲线
            plt.plot(daily_dates, daily_values, label='策略收益', linewidth=2)
            
            # 设置标题
            if title:
                plt.title('策略表现', fontsize=14, pad=20)
            plt.xlabel('日期', fontsize=12)
            plt.ylabel('组合价值', fontsize=12)
            plt.grid(True, linestyle='--', alpha=0.7)
            plt.legend(fontsize=10)
            
            # Optimize display
            plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
            plt.gcf().autofmt_xdate()
            
            # Add performance metrics annotation
            annotation_text = (
                f'总收益率: {total_return:+.2f}%\n'
                f'最大回撤: {max_drawdown:.2f}%'
            )
            plt.figtext(0.01, 0.01, annotation_text,
                       fontsize=10,
                       bbox=dict(facecolor='white', 
                                edgecolor='gray',
                                alpha=0.8,
                                pad=5))  # 增加内边距
            
            # Save plot
            save_path = os.path.join(self.config.result_path, 'strategy_returns.png')
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
            self.logger.info(f"Returns curve saved to: {save_path}")
            
            # Display plot
            plt.show()
            plt.close()
            
            # Save detailed results to CSV
            results_df = pd.DataFrame({
                'Date': daily_dates,
                'Portfolio_Value': daily_values
            })
            csv_path = os.path.join(self.config.result_path, 'daily_returns.csv')
            results_df.to_csv(csv_path, index=False)
            self.logger.info(f"Daily returns data saved to: {csv_path}")
            self.logger.info("=" * 50)
            
            # Plot position counts
            self.plot_position_counts(daily_dates, daily_position_counts)
            
            # 在现有的分析完成后，添加持股天数与收益关系分析
            self.plot_holding_days_returns(strategy.trade_records)
            
            return True
            
        except Exception as e:
            self.logger.error(f"Result analysis failed: {str(e)}")
            return False

    def plot_position_counts(self, daily_dates, daily_position_counts):
        """Plot daily position count changes"""
        try:
            # 创建图形
            plt.figure(figsize=(12, 6))
            
            # Plot position count curve
            plt.plot(daily_dates, daily_position_counts, 
                    label='持仓数量', color='blue', linewidth=2)
            
            # Set graph format
            plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
            plt.gcf().autofmt_xdate()  # Auto-rotate date labels
            
            plt.grid(True, linestyle='--', alpha=0.7)
            plt.title('每日持仓数量变化趋势', fontsize=14, pad=20)
            plt.xlabel('交易日期', fontsize=12)
            plt.ylabel('持仓数量', fontsize=12)
            plt.legend(fontsize=10)
            
            # Add statistics
            avg_positions = np.mean(daily_position_counts)
            max_positions = max(daily_position_counts)
            min_positions = min(daily_position_counts)
            
            info_text = (f'平均持仓: {avg_positions:.1f}\n'
                        f'最大持仓: {max_positions}\n'
                        f'最小持仓: {min_positions}')
            
            plt.text(0.02, 0.98, info_text,
                    transform=plt.gca().transAxes,
                    verticalalignment='top',
                    bbox=dict(facecolor='white', alpha=0.8))
            
            # Save plot
            save_path = os.path.join(self.config.result_path, 'position_counts.png')
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
            self.logger.info(f"Position count chart saved to: {save_path}")
            
            # Display plot
            plt.show()
            plt.close()
            
        except Exception as e:
            self.logger.error(f"Failed to plot position count chart: {str(e)}")

    def plot_stock_pool_size(self, stock_pool_sizes_dict):
        """绘制股票池规模随时间变化的图表"""
        try:
            plt.figure(figsize=(12, 6))
            
            # 将字典转换为有序的日期和大小列表
            dates = sorted(stock_pool_sizes_dict.keys())
            sizes = [stock_pool_sizes_dict[date] for date in dates]
            
            # 绘制股票池规模曲线
            plt.plot(dates, sizes, 
                    label='股票池规模', color='blue', linewidth=2)
            
            # 设置图表格式
            plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
            plt.gcf().autofmt_xdate()  # 自动旋转日期标签
            
            plt.grid(True, linestyle='--', alpha=0.7)
            plt.title('股票池规模变化')
            plt.xlabel('日期')
            plt.ylabel('股票数量')
            
            # 添加统计信息
            avg_size = np.mean(sizes)
            max_size = max(sizes)
            min_size = min(sizes)
            
            info_text = (f'股票池规模\n'
                        f'平均: {avg_size:.1f}\n'
                        f'最大: {max_size}\n'
                        f'最小: {min_size}')
            
            plt.text(0.02, 0.98, info_text,
                    transform=plt.gca().transAxes,
                    verticalalignment='top',
                    bbox=dict(facecolor='white', alpha=0.8, 
                            edgecolor='gray', pad=5))
            
            # 保存图表
            save_path = os.path.join(self.config.result_path, 'stock_pool_size.png')
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
            self.logger.info(f"股票池规模图表已保存至: {save_path}")
            
            plt.show()
            plt.close()
            
        except Exception as e:
            self.logger.error(f"绘制股票池规模图表失败: {str(e)}")

    def plot_holding_days_returns(self, trade_records):
        """绘制持股天数与收益率的关系图"""
        try:
            # 提取每笔交易的持股天数和收益率
            holding_data = []
            
            for i, trade in enumerate(trade_records):
                if trade['type'] == '卖出':
                    # 找到对应的买入记录
                    buy_trade = None
                    for prev_trade in trade_records[:i]:
                        if (prev_trade['type'] == '买入' and 
                            prev_trade['ts_code'] == trade['ts_code']):
                            buy_trade = prev_trade
                            break
                    
                    if buy_trade:
                        # 计算持股天数
                        holding_days = (trade['date'] - buy_trade['date']).days
                        # 计算收益率
                        returns = (trade['price'] - buy_trade['price']) / buy_trade['price'] * 100
                        
                        holding_data.append({
                            'holding_days': holding_days,
                            'returns': returns,
                            'ts_code': trade['ts_code'],
                            'profit': returns > 0
                        })
            
            if not holding_data:
                self.logger.warning("没有足够的交易数据来绘制持股天数-收益关系图")
                return
            
            # 创建图形
            plt.figure(figsize=(12, 8))
            
            # 分别绘制盈利和亏损的交易点
            profit_trades = [t for t in holding_data if t['profit']]
            loss_trades = [t for t in holding_data if not t['profit']]
            
            # 绘制盈利交易点（红色）
            if profit_trades:
                plt.scatter([t['holding_days'] for t in profit_trades],
                          [t['returns'] for t in profit_trades],
                          c='red', alpha=0.6, label='盈利交易')

            
            # 绘制亏损交易点（绿色）
            if loss_trades:
                plt.scatter([t['holding_days'] for t in loss_trades],
                          [t['returns'] for t in loss_trades],
                          c='green', alpha=0.6, label='亏损交易')

            
            # 添加趋势线
            all_days = [t['holding_days'] for t in holding_data]
            all_returns = [t['returns'] for t in holding_data]
            
            if len(all_days) > 1:  # 确保有足够的数据点进行拟合
                z = np.polyfit(all_days, all_returns, 1)
                p = np.poly1d(z)
                plt.plot(all_days, p(all_days), "b--", alpha=0.8, label='趋势线')
            
            # 计算统计信息
            avg_profit_days = np.mean([t['holding_days'] for t in profit_trades]) if profit_trades else 0
            avg_loss_days = np.mean([t['holding_days'] for t in loss_trades]) if loss_trades else 0
            
            # 添加统计信息文本
            stats_text = (
                f'盈利交易平均持股天数: {avg_profit_days:.1f}\n'
                f'亏损交易平均持股天数: {avg_loss_days:.1f}\n'
                f'盈利交易数: {len(profit_trades)}\n'
                f'亏损交易数: {len(loss_trades)}'
            )
            
            plt.text(0.02, 0.98, stats_text,
                    transform=plt.gca().transAxes,
                    verticalalignment='top',
                    bbox=dict(facecolor='white', alpha=0.8))
            
            # 设置图形格式
            plt.grid(True, linestyle='--', alpha=0.7)
            plt.title('持股天数与收益率关系分析', fontsize=14, pad=20)
            plt.xlabel('持股天数', fontsize=12)
            plt.ylabel('收益率(%)', fontsize=12)
            plt.legend()
            
            # 添加零收益线
            plt.axhline(y=0, color='gray', linestyle='-', alpha=0.3)
            
            # 保存图片
            save_path = os.path.join(self.config.result_path, 'holding_days_returns.png')
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
            self.logger.info(f"Holding days-returns chart saved to: {save_path}")
            
            # 显示图形
            plt.show()
            plt.close()
            
        except Exception as e:
            self.logger.error(f"绘制持股天数-收益关系图失败: {str(e)}")

class BacktestSystem:
    """回测系统"""
    def __init__(self, strategy, stock_list, start_date, end_date, initial_capital, config):
        """初始化回测系统"""
        self.strategy = strategy
        self.stock_list = stock_list
        self.start_date = pd.to_datetime(start_date)
        self.end_date = pd.to_datetime(end_date)
        self.initial_capital = initial_capital
        self.config = config
        
        self.logger = logging.getLogger(__name__)
        self.stock_data = {}
        self.trade_dates = []
        
        # 创建结果目录
        if not os.path.exists(self.config.result_path):
            os.makedirs(self.config.result_path)
        
        # 准备数据和交易日历
        if not self.prepare_data():
            raise RuntimeError("数据准备失败")
            
        # 初始化策略
        self.strategy.initialize(
            self.stock_data,
            self.trade_dates,
            self.initial_capital
        )
        
        # 添加每日持仓数量记录
        self.daily_position_counts = []
        
        # 添加空仓统计相关的属性
        self.no_position_dates = []  # 记录空仓的日期
        self.total_days = 0          # 总交易天数

    def prepare_data(self):
        """准备回测数据"""
        try:
            self.logger.info("正在从本地文件加载数据...")
            stock_codes = self.stock_list['ts_code'].tolist()
            all_dates = set()
            
            # 获取股票数据
            with tqdm(total=len(stock_codes), desc="加载股票数据", ncols=100) as pbar:
                for ts_code in stock_codes:
                    # 构建文件路径
                    daily_file = os.path.join(self.config.daily_path, f"{ts_code}.csv")
                    basic_file = os.path.join(self.config.basic_path, f"{ts_code}_basic.csv")
                    
                    try:
                        # 读取日线数据
                        daily_data = pd.read_csv(daily_file)
                        daily_data['trade_date'] = pd.to_datetime(daily_data['trade_date'].astype(str))
                        
                        # 读取基本面数据
                        if os.path.exists(basic_file):
                            basic_data = pd.read_csv(basic_file)
                            basic_data['trade_date'] = pd.to_datetime(basic_data['trade_date'].astype(str))
                            
                            # 移除基本面数据中与日线数据重复的列
                            duplicate_cols = set(daily_data.columns) & set(basic_data.columns)
                            cols_to_drop = [col for col in duplicate_cols if col not in ['trade_date', 'ts_code']]
                            basic_data = basic_data.drop(columns=cols_to_drop)
                            
                            # 合并日线和基本面数据
                            df = pd.merge(daily_data, basic_data, on='trade_date', how='left')
                        else:
                            df = daily_data
                            self.logger.debug(f"找不到基本面数据: {basic_file}")
                        
                        # 获取日期范围内的数据
                        mask = (df['trade_date'] >= self.start_date) & \
                               (df['trade_date'] <= self.end_date)
                        stock_data = df[mask].copy()
                        
                        if not stock_data.empty:
                            # 设置日期索引
                            stock_data.set_index('trade_date', inplace=True)
                            stock_data.sort_index(inplace=True)
                            
                            # 选择需要的列
                            daily_cols = ['open', 'high', 'low', 'close', 'vol', 'amount',
                                        'vol_ma5', 'vol_ma10', 'vol_ma20', 'vol_ma30', 'vol_ma40', 'vol_ma50', 'vol_ma60',
                                        'ma5', 'ma10', 'ma20', 'ma30', 'ma60', 'ma120', 'ma240']
                            
                            # 添加基本面数据列（如果存在）
                            basic_cols = ['turnover_rate_f', 'volume_ratio', 'pe_ttm', 'ps_ttm',
                                        'total_mv', 'circ_mv']
                            cols_to_use = daily_cols + [col for col in basic_cols if col in stock_data.columns]
                            
                            stock_data = stock_data[cols_to_use]
                            
                            # 计算技术指标
                            stock_data = TechnicalAnalysis.calculate_indicators(stock_data, self.config)
                            self.stock_data[ts_code] = stock_data
                            
                            # 收集交易日期
                            all_dates.update(stock_data.index)
                            
                    except FileNotFoundError:
                        self.logger.warning(f"找不到股票数据文件: {daily_file}")
                    except Exception as e:
                        self.logger.warning(f"处理股票 {ts_code} 数据时出错: {str(e)}")
                        
                    pbar.update(1)
            
            # 设置交易日历
            self.trade_dates = sorted(list(all_dates))
            
            # 检查数据是否足够
            if len(self.stock_data) == 0:
                self.logger.error("没有获取到有效的股票数据")
                return False
            
            if len(self.trade_dates) == 0:
                self.logger.error("所选时间范围内没有交易数据")
                return False
            
            self.logger.info(f"成功加载 {len(self.stock_data)} 只股票的数据")
            self.logger.info(f"交易日期范围: {self.trade_dates[0].strftime('%Y-%m-%d')} "
                           f"至 {self.trade_dates[-1].strftime('%Y-%m-%d')}")
            self.logger.info(f"总交易日数: {len(self.trade_dates)}")
            
            return True
            
        except Exception as e:
            self.logger.error(f"准备数据时发生错误: {str(e)}")
            return False

    def run_backtest(self):
        """运行回测"""
        self.logger.info("开始回测...")
        self.logger.info(f"初始资金: {self.initial_capital:,.2f}")
        
        self.total_days = len(self.trade_dates)  # 记录总交易天数
        
        # 遍历每个交易日
        for current_date in tqdm(self.trade_dates, desc="回测进度", ncols=100):
            # 更新当前日期的数据
            current_data = {
                code: data.loc[current_date] 
                for code, data in self.stock_data.items() 
                if current_date in data.index
            }
            
            # 执行策略
            self.strategy.on_data(current_date, current_data)
            
            # 记录当前持仓数量
            self.daily_position_counts.append(len(self.strategy.positions))
            
            # 记录空仓日期
            if len(self.strategy.positions) == 0:
                self.no_position_dates.append(current_date)
        
        # 输出空仓统计信息
        self._print_no_position_stats()
        
        self.logger.info("回测完成")
        return True

    def _print_no_position_stats(self):
        """输出空仓统计信息"""
        no_position_days = len(self.no_position_dates)
        no_position_ratio = (no_position_days / self.total_days) * 100
        
        self.logger.info("\n空仓统计信息:")
        self.logger.info("-" * 50)
        self.logger.info(f"总交易天数: {self.total_days} 天")
        self.logger.info(f"空仓天数: {no_position_days} 天")
        self.logger.info(f"空仓占比: {no_position_ratio:.2f}%")
        
        if no_position_days > 0:
            self.logger.info("\n空仓期间明细:")
            self.logger.info("-" * 50)
            self.logger.info("开始日期    结束日期    持续天数")
            self.logger.info("-" * 50)
            
            # 统计连续空仓期间
            no_position_periods = []
            start_date = self.no_position_dates[0]
            prev_date = self.no_position_dates[0]
            
            for date in self.no_position_dates[1:] + [None]:
                if date is None or (date - prev_date).days > 1:
                    # 当前空仓期间结束
                    duration = (prev_date - start_date).days + 1
                    no_position_periods.append({
                        'start': start_date,
                        'end': prev_date,
                        'duration': duration
                    })
                    if date is not None:
                        start_date = date
                prev_date = date
            
            # 按持续时间排序并输出
            no_position_periods.sort(key=lambda x: x['duration'], reverse=True)
            for period in no_position_periods:
                self.logger.info(f"{period['start'].strftime('%Y-%m-%d')}  "
                               f"{period['end'].strftime('%Y-%m-%d')}  "
                               f"{period['duration']:>4} 天")
        
        self.logger.info("-" * 50)

    def _print_backtest_results(self, final_value, duration):
        """输出回测结果"""
        self.logger.info("\n回测统计:")
        self.logger.info(f"初始资金: {self.initial_capital:,.2f} 元")
        self.logger.info(f"最终市值: {final_value:,.2f} 元")
        self.logger.info(f"总收益率: {((final_value/self.initial_capital)-1)*100:.2f}%")
        self.logger.info(f"最大回撤: {self.strategy.max_drawdown*100:.2f}%")
        
        days = len(self.strategy.daily_dates)
        if days > 0:
            annual_return = (final_value/self.initial_capital) ** (365/days) - 1
            self.logger.info(f"年化收益率: {annual_return*100:.2f}%")
        
        self.logger.info(f"\n回测耗时: {duration}")
        self.logger.info(f"回测股票数量: {len(self.stock_list)}")

    def get_daily_data(self, date):
        """获取某一天的交易数据"""
        daily_data = {}
        
        for ts_code, df in self.stock_data.items():
            if date in df.index:
                daily_data[ts_code] = df.loc[date]
                
        return daily_data

class MyStrategy:
    """交易策略类"""
    def __init__(self, config):
        self.config = config
        self.logger = logging.getLogger(__name__)
        
        # 初始化策略状态
        self.positions = {}       # 当前持仓
        self.cash = 0            # 当前现金
        self.current_value = 0   # 当前总价值
        
        # 记录每日数据
        self.daily_values = []   # 每日总价值
        self.daily_dates = []    # 对应的日期
        self.max_value = 0       # 最大总价值
        self.max_drawdown = 0    # 最大回撤
        self.daily_stock_pool_sizes_dict = {}  # 每日股票池规模
        
        # 初始化交易记录
        self.trade_records = []  # 交易记录列表
        self.position_high_prices = {}  # 记录每个持仓的最高价
        self.position_days = {}  # 记录每个持仓的持有天数
        
        # 初始化基准数据
        self.benchmark_returns = []  # 基准收益率
        self.start_cash = self.config.initial_capital  # 初始资金
        
        # 初始化交易参数
        self.stop_loss_pct = self.config.stop_loss_pct  # 止损线
        self.stop_profit_pct = self.config.stop_profit_pct  # 止盈线
        self.trailing_stop_pct = self.config.trailing_stop_pct  # 追踪止损
        
        # 初始化股票相关数据
        self.stock_data = {}  # 股票数据字典
        self.trade_dates = []  # 交易日期列表
        self.stock_names = {}  # 股票名称字典
        
        # 初始化日收益率列表
        self.daily_returns = []  # 每日收益率
        
        # 动态仓位管理相关
        self.current_position_pct = self.config.max_position_pct  # 当前单个持仓的目标仓位比例
        self.last_high_value = None  # 记录历史最高净值
        self.last_low_value = None   # 记录触发减仓的净值

    def initialize(self, stock_data, trade_dates, initial_capital):
        """初始化策略"""
        self.stock_data = stock_data
        self.trade_dates = trade_dates
        self.cash = initial_capital
        self.current_value = initial_capital
        self.max_value = initial_capital
        
        self.logger.info("策略初始化完成")
        self.logger.info(f"交易日期范围: {trade_dates[0].strftime('%Y-%m-%d')} - {trade_dates[-1].strftime('%Y-%m-%d')}")
        self.logger.info(f"可交易股票数量: {len(stock_data)}")

    def set_stock_names(self, stock_names):
        """设置股票名称字典"""
        self.stock_names = stock_names
        
    def set_trade_dates(self, trade_dates):
        """设置交易日历"""
        self.trade_dates = trade_dates
        
    def get_trade_days_between(self, start_date, end_date):
        """计算两个日期之间的交易日数量"""
        if not isinstance(start_date, pd.Timestamp):
            start_date = pd.to_datetime(start_date)
        if not isinstance(end_date, pd.Timestamp):
            end_date = pd.to_datetime(end_date)
        
        # 获取在交易日历中的日期
        trade_days = len([d for d in self.trade_dates 
                         if start_date <= d <= end_date])
        return trade_days - 1  # 减1是因为不计算买入当天

    def record_trade(self, date, ts_code, trade_type, price, volume, reason=None):
        """记录交易信息"""
        trade = {
            'date': date,
            'ts_code': ts_code,
            'type': trade_type,
            'price': price,
            'volume': volume,
            'value': price * volume,
            'reason': reason
        }
        self.trade_records.append(trade)
        self.logger.info(f"记录{trade_type}交易: {ts_code}, 价格: {price:.2f}, 数量: {volume}, 原因: {reason}")

    def _check_base_sell_signals(self, date, ts_code, pos, data, total_value):
        """检查基础卖出信号的通用逻辑"""
        entry_price = pos['entry_price']
        profit_pct = (data - entry_price) / entry_price
        position_pct = pos['shares'] * data / total_value
        
        # 更新最高价
        if ts_code not in self.position_high_prices:
            self.position_high_prices[ts_code] = data
        else:
            self.position_high_prices[ts_code] = max(self.position_high_prices[ts_code], data)
        
        high_price = self.position_high_prices[ts_code]
        
        # 基础卖出条件检查
        if profit_pct <= self.stop_loss_pct:
            return "止损", profit_pct
        
        if (high_price - data) / high_price >= self.trailing_stop_pct:
            return "追踪止损", profit_pct
        
        if self.position_days[ts_code] >= self.config.max_hold_days:
            return "持仓到期", profit_pct
        
        if profit_pct >= self.stop_profit_pct:
            return "止盈", profit_pct
        
        return None, profit_pct

    def check_buy_signals(self, stock, data_dict, total_value, available_cash):
        """检查买入信号"""
        ts_code = stock['ts_code']
        current_price = stock['price']
        
        # 计算目标仓位
        target_position = total_value * self.max_position_pct
        
        # 计算可买数量（向下取整到100的整数倍）
        max_volume = int(min(target_position, available_cash) / current_price / 100) * 100
        
        if max_volume < 100:  # 如果不够买100股，放弃买入
            return None
        
        # 检查买入条件
        buy_signal = True
        
        if buy_signal:
            return {
                'volume': max_volume,
                'price': current_price,
                'value': max_volume * current_price
            }
        
        return None

    def check_sell_signals_at_price(self, date, ts_code, pos, data, total_value, price):
        """在指定价格点检查卖出信号"""
        sell_reason, profit_pct = self._check_base_sell_signals(
            date, ts_code, pos, price, total_value
        )
        
        if sell_reason:
            return f"区间价格{price:.2f}触发{sell_reason}", profit_pct
        
        return None, profit_pct

    def on_data(self, current_date, current_data):
        """处理每个交易日的数据"""
        # 更新持仓价值
        total_value = self.cash
        closed_positions = []
        
        # 使用字典的副本进行遍历
        positions_items = list(self.positions.items())
        
        # 更新所有持仓的持仓天数
        for code, position in positions_items:
            if code not in self.position_days:
                self.position_days[code] = 0
            self.position_days[code] += 1
        
        # 更新持仓价值并检查卖出信号
        for code, position in positions_items:
            if code in current_data and code not in closed_positions:
                data = current_data[code]
                current_price = data['close']
                position_value = position['shares'] * current_price
                total_value += position_value
                
                # 在日内价格区间上进行扫描
                low_price = data['low']
                high_price = data['high']
                
                # 创建包含关键价格点的价格列表
                key_prices = [round(low_price, 2), round(high_price, 2)]  # 确保包含最高价和最低价
                
                # 计算中间价格点
                price_step = round((high_price - low_price) / 98, 2)  # 减少2个点位，因为已经包含了最高价和最低价
                middle_prices = [round(low_price + i * price_step, 2) for i in range(1, 99)]  # 生成98个中间价格点
                
                # 合并所有价格点并排序
                scan_prices = sorted(list(set(key_prices + middle_prices)))  # 使用set去除可能的重复值
                
                # 遍历所有价格点
                for scan_price in scan_prices:
                    # 检查当前价格点是否触发卖出信号
                    sell_reason, profit_pct = self.check_sell_signals_at_price(
                        current_date, code, position, data, total_value, scan_price
                    )
                    
                    if sell_reason:
                        # 触发卖出信号时，使用收盘价执行卖出
                        self.close_position(code, data['close'], current_date, sell_reason)
                        closed_positions.append(code)
                        break  # 一旦触发卖出，就不再继续检查该股票

        # 选股
        if len(self.positions) < self.config.max_positions:
            selected_stocks = self.select_stocks(current_data, current_date)
            
            # 开仓（使用当前的动态仓位比例）
            for code in selected_stocks:
                if code not in self.positions and len(self.positions) < self.config.max_positions:
                    self.open_position(code, current_data[code]['close'], current_date)
        
        # 更新策略状态
        self.current_value = total_value
        self.max_value = max(self.max_value, total_value)
        self.max_drawdown = max(self.max_drawdown, 1 - total_value / self.max_value)
        
        # 调整仓位大小
        self.adjust_position_size()
        
        # 记录每日数据
        self.daily_dates.append(current_date)
        self.daily_values.append(total_value)
        
    def check_stop_conditions(self, code, position, current_price):
        """检查止损止盈条件"""
        if not position:
            return False
            
        entry_price = position['entry_price']
        profit_pct = (current_price / entry_price - 1)
        
        # 止损
        if profit_pct <= self.config.stop_loss_pct:
            self.logger.info(f"触发止损: {code}, 收益率: {profit_pct:.2%}")
            return True
            
        # 止盈
        if profit_pct >= self.config.stop_profit_pct:
            self.logger.info(f"触发止盈: {code}, 收益率: {profit_pct:.2%}")
            return True
            
        # 追踪止损
        if position.get('max_price', entry_price) < current_price:
            position['max_price'] = current_price
        elif (current_price / position['max_price'] - 1) <= -self.config.trailing_stop_pct:
            self.logger.info(f"触发追踪止损: {code}, 收益率: {profit_pct:.2%}")
            return True
            
        return False
        
    def select_stocks(self, current_data, current_date):
        """
        选股函数：根据均线系统和基本面指标筛选股票
        
        筛选条件：
        1. 均线系统：
            - 120日均线在240日均线之上
            - 60日均线在120日均线之上
            - 30日均线在60日均线之上
            - 股价在20日均线之上
        2. 基本面指标：
            - 市盈率(PE) <= 50
            - 市销率(PS) <= 10
            - 换手率(turnover_rate_f) <= 15%
        3. 价格区间：
            - 股价在5元到100元之间
            
        参数:
            current_data: 当前交易日所有股票的数据
            current_date: 当前交易日期

        返回:
            selected: 筛选出的股票代码列表，数量不超过可用持仓位置数
        """
        selected = []  # 存储筛选后的股票代码
        stock_info = []  # 存储股票信息用于排序
        
        # 遍历所有股票数据
        for code, data in current_data.items():
            # 跳过已持仓的股票
            if code in self.positions:
                continue
            
            # 1. 股票代码筛选（主板）
            if not (code.startswith('600') or code.startswith('601') or 
                    code.startswith('603') or code.startswith('605') or 
                    code.startswith('000')):
                continue
            
            # 2. 跳过ST股票
            if 'ST' in data.get('name', '').upper():
                continue
            
            # 3. 均线系统条件判断
            if not (data['ma120'] > data['ma240'] and  # 120日均线在240日均线之上
                    data['ma60'] > data['ma120'] and   # 60日均线在120日均线之上
                    data['ma30'] > data['ma60'] and    # 30日均线在60日均线之上
                    data['ma20'] > data['ma30'] and    # 20日均线在30日均线之上
                    data['ma10'] > data['ma20'] and    # 10日均线在20日均线之上
                    data['ma5'] > data['ma10'] and     # 5日均线在10日均线之上
                    data['close'] > data['ma20']):     # 股价在20日均线之上
                continue

            # 4. 基本面指标筛选
            # 市盈率(PE)检查
            if 'pe_ttm' not in data or data['pe_ttm'] <= 0 or data['pe_ttm'] > 50:
                continue
                
            # 市销率(PS)检查
            if 'ps_ttm' not in data or data['ps_ttm'] <= 0 or data['ps_ttm'] > 10:
                continue
            
            # 5. 成交量筛选
            # 成交量需小于N日平均成交量的R倍                
            vol_value = data['vol']
            if not (vol_value <= data['vol_ma60'] * self.config.vol_ratio_threshold and
                    vol_value <= data['vol_ma50'] * self.config.vol_ratio_threshold and
                    vol_value <= data['vol_ma40'] * self.config.vol_ratio_threshold and
                    vol_value <= data['vol_ma30'] * self.config.vol_ratio_threshold and
                    vol_value <= data['vol_ma20'] * self.config.vol_ratio_threshold and
                    vol_value <= data['vol_ma10'] * self.config.vol_ratio_threshold and
                    vol_value <= data['vol_ma5'] * self.config.vol_ratio_threshold):
                continue

            # 价格区间检查
            if data['close'] < self.config.min_price or data['close'] > self.config.max_price:
                continue
            
            # 换手率检查
            if ('turnover_rate_f' not in data or 
                data['turnover_rate_f'] > self.config.max_turnover_rate_f):
                continue
                        
            # 添加到股票信息列表
            stock_info.append({
                'code': code,
                'volume': vol_value,
                'price': data['close'],
                'name': data.get('name', '未知')
            })
                                                
        # 检查股票池大小，如果小于1则返回空列表
        if len(stock_info) < 1:
            self.daily_stock_pool_sizes_dict[current_date] = 0  # 记录空股票池
            return []
        
        # 按成交量从大到小排序
        sorted_stocks = sorted(stock_info, key=lambda x: x['volume'], reverse=True)
        
        # 记录当前日期的股票池大小
        self.daily_stock_pool_sizes_dict[current_date] = len(sorted_stocks)
        
        # 打印股票池信息
        self.logger.info("\n当前股票池信息:")
        self.logger.info("-" * 80)
        self.logger.info(f"股票池大小: {len(sorted_stocks)} 只股票")
        self.logger.info("\n成交量排名前20股票:")
        self.logger.info(f"{'排名':<6}{'代码':<12}{'名称':<12}{'成交量':<15}{'当前价格':<10}")
        self.logger.info("-" * 80)
        
        # 打印前20只股票的详细信息
        for i, stock in enumerate(sorted_stocks[:20], 1):
            self.logger.info(f"{i:<6}{stock['code']:<12}{stock['name']:<12}"
                            f"{stock['volume']:<15,.0f}{stock['price']:<10.2f}")
        self.logger.info("-" * 80)
        
        # 返回筛选结果，但不超过最大可开仓数量
        selected = [stock['code'] for stock in sorted_stocks]
        
        # 返回筛选结果，但不超过最大可开仓数量
        return selected[:self.config.max_positions - len(self.positions)]
        
    def open_position(self, code, price, date):
        """开仓（使用动态仓位）"""
        if code in self.positions:
            return
            
        # 使用当前动态仓位计算持仓量
        position_value = self.current_value * self.current_position_pct
        # 将股票数量向下取整到最接近的100的倍数
        shares = int(position_value / price / 100) * 100
        
        # 如果股票数量为0，则不开仓
        if shares == 0:
            return
            
        cost = shares * price
        
        if cost > self.cash:
            return
            
        self.positions[code] = {
            'shares': shares,
            'entry_price': price,
            'entry_date': date,
            'max_price': price
        }
        self.cash -= cost
        
        # 记录买入交易
        self.record_trade(date, code, '买入', price, shares, '开仓')
        
        self.logger.info(f"开仓: {code}, 价格: {price:.2f}, 数量: {shares}, 仓位: {self.current_position_pct:.2%}")
        
    def close_position(self, code, price, date, reason):
        """平仓"""
        if code not in self.positions:
            return
            
        position = self.positions[code]
        shares = position['shares']
        entry_price = position['entry_price']
        
        # 计算收益
        profit = shares * (price - entry_price)
        profit_pct = (price / entry_price - 1)
        
        # 更新现金
        self.cash += shares * price
        
        # 记录卖出交易
        self.record_trade(date, code, '卖出', price, shares, reason)
        
        # 移除持仓和持仓天数记录
        del self.positions[code]
        if code in self.position_days:
            del self.position_days[code]
        if code in self.position_high_prices:
            del self.position_high_prices[code]
        
        self.logger.info(f"平仓: {code}, 价格: {price:.2f}, 收益率: {profit_pct:.2%}, 原因: {reason}")

    def adjust_position_size(self):
        """根据账户净值变化动态调整仓位"""
        # 如果动态仓位管理未启用，直接返回
        if not self.config.enable_dynamic_position:
            return

        if self.last_high_value is None:
            self.last_high_value = self.current_value
            self.last_low_value = self.current_value
            return

        # 计算相对于最高点的回撤
        if self.current_value > self.last_high_value:
            # 创新高，检查是否需要增加仓位
            value_increase = self.current_value / self.last_low_value - 1
            if value_increase >= self.config.position_increase_threshold:
                # 增加仓位
                new_position_pct = self.current_position_pct + self.config.position_increase_step
                # 确保不超过最大仓位限制
                self.current_position_pct = min(new_position_pct, self.config.max_position_pct)
                self.last_low_value = self.current_value
                self.logger.info(f"账户净值上涨{value_increase:.2%}，调整目标仓位至{self.current_position_pct:.2%}")
            self.last_high_value = self.current_value
        else:
            # 计算回撤幅度
            drawdown = 1 - self.current_value / self.last_high_value
            if drawdown >= self.config.position_reduce_threshold:
                # 减少仓位
                new_position_pct = self.current_position_pct - self.config.position_reduce_step
                # 确保不低于最小仓位限制
                self.current_position_pct = max(new_position_pct, self.config.min_position_pct)
                self.last_high_value = self.current_value
                self.logger.info(f"账户净值回撤{drawdown:.2%}，调整目标仓位至{self.current_position_pct:.2%}")

    def plot_returns(self):
        """绘制收益曲线"""
        try:
            # 创建图形
            plt.figure(figsize=(12, 8))
            
            # 绘制策略收益曲线
            dates = [pd.to_datetime(d) for d in self.daily_dates]
            strategy_returns = [r * 100 for r in self.daily_returns]
            plt.plot(dates, strategy_returns, label='策略收益', color='red', linewidth=2)
            
            # 绘制基准收益曲线
            if self.benchmark_returns:
                benchmark_returns = [r * 100 for r in self.benchmark_returns]
                plt.plot(dates, benchmark_returns, label='上证指数', color='blue', linewidth=2)
            
            # 设置图形格式
            plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
            plt.gcf().autofmt_xdate()  # 自动旋转日期标签
            
            plt.grid(True, linestyle='--', alpha=0.7)
            plt.title('策略收益曲线', fontsize=14)
            plt.xlabel('日期', fontsize=12)
            plt.ylabel('收益率(%)', fontsize=12)
            plt.legend()
            
            # 添加收益统计信息
            final_return = strategy_returns[-1]
            max_drawdown = self.max_drawdown * 100
            days = len(self.daily_dates)
            annual_return = (self.current_value/self.start_cash) ** (365/days) - 1
            
            info_text = (f'总收益率: {final_return:.2f}%\n'
                        f'年化收益率: {annual_return*100:.2f}%\n'
                        f'最大回撤: {max_drawdown:.2f}%')
            
            plt.text(0.02, 0.98, info_text,
                    transform=plt.gca().transAxes,
                    verticalalignment='top',
                    bbox=dict(facecolor='white', alpha=0.8))
            
            # 保存图片
            save_path = os.path.join(self.config.result_path, 'strategy_returns.png')
            plt.savefig(save_path, dpi=300, bbox_inches='tight')
            self.logger.info("收益曲线图已保存至 logs/strategy_returns.png")
            
            # 显示图形
            plt.show()
            
        except Exception as e:
            self.logger.error(f"绘制收益曲线失败: {str(e)}")

    def on_finish(self):
        """回测结束时的处理"""
        # 基础统计信息
        self.logger.info("\n策略统计:")
        total_return = (self.current_value / self.start_cash - 1) * 100
        self.logger.info(f"策略收益率: {total_return:.2f}%")
        self.logger.info(f"最大回撤: {self.max_drawdown*100:.2f}%")
        
        days = len(self.daily_dates)
        if days > 0:
            annual_return = (self.current_value/self.start_cash) ** (365/days) - 1
            self.logger.info(f"年化收益率: {annual_return*100:.2f}%")
        
        # 计算每笔交易的盈亏
        closed_trades = []
        for i, trade in enumerate(self.trade_records):
            if trade['type'] == '卖出':
                # 找到对应的买入记录
                buy_trade = None
                for prev_trade in self.trade_records[:i]:
                    if (prev_trade['type'] == '买入' and 
                        prev_trade['ts_code'] == trade['ts_code']):
                        buy_trade = prev_trade
                        break
                
                if buy_trade:
                    profit_amount = (trade['price'] - buy_trade['price']) * trade['volume'] if trade['type'] == '卖出' else 0
                    profit_pct = (trade['price'] - buy_trade['price']) / buy_trade['price']
                    closed_trades.append({
                        'ts_code': trade['ts_code'],
                        'name': trade['name'],
                        'buy_date': buy_trade['date'].strftime('%Y-%m-%d'),
                        'buy_price': buy_trade['price'],
                        'sell_date': trade['date'].strftime('%Y-%m-%d'),
                        'sell_price': trade['price'],
                        'volume': trade['volume'],
                        'profit_amount': profit_amount,
                        'profit_pct': profit_pct,
                        'reason': trade['reason']
                    })
        
        # 交易统计
        total_trades = len(closed_trades)
        if total_trades > 0:
            profit_trades = sum(1 for t in closed_trades if t['profit_amount'] > 0)
            loss_trades = sum(1 for t in closed_trades if t['profit_amount'] <= 0)
            total_profit = sum(t['profit_amount'] for t in closed_trades if t['profit_amount'] > 0)
            total_loss = sum(t['profit_amount'] for t in closed_trades if t['profit_amount'] <= 0)
            
            self.logger.info("\n交易统计:")
            self.logger.info(f"总交易次数: {total_trades}")
            self.logger.info(f"盈利次数: {profit_trades}")
            self.logger.info(f"亏损次数: {loss_trades}")
            self.logger.info(f"胜率: {profit_trades/total_trades*100:.2f}%")
            self.logger.info(f"总盈利: {total_profit:.2f}")
            self.logger.info(f"总亏损: {total_loss:.2f}")
            if loss_trades > 0:
                self.logger.info(f"盈亏比: {abs(total_profit/total_loss):.2f}")
        
        # 按盈利金额排序
        sorted_trades = sorted(closed_trades, key=lambda x: x['profit_amount'])
        
        # 输出TOP10亏损
        self.logger.info("\nTOP10亏损交易:")
        self.logger.info("-" * 100)
        self.logger.info(f"{'股票代码':<12}{'股票名称':<12}{'买入日期':<12}{'买入价格':<10}{'卖出日期':<12}"
                        f"{'卖出价格':<10}{'交易量':<10}{'盈亏金额':<12}{'收益率':<10}{'卖出原因':<12}")
        self.logger.info("-" * 100)
        
        for trade in sorted_trades[:10]:  # TOP10亏损
            self.logger.info(
                f"{trade['ts_code']:<12}{trade['name']:<12}{trade['buy_date']:<12}"
                f"{trade['buy_price']:<10.2f}{trade['sell_date']:<12}{trade['sell_price']:<10.2f}"
                f"{trade['volume']:<10}{trade['profit_amount']:<12.2f}"
                f"{trade['profit_pct']*100:<10.2f}%{trade['reason']:<12}"
            )
        
        # 输出TOP10盈利
        self.logger.info("\nTOP10盈利交易:")
        self.logger.info("-" * 100)
        self.logger.info(f"{'股票代码':<12}{'股票名称':<12}{'买入日期':<12}{'买入价格':<10}{'卖出日期':<12}"
                        f"{'卖出价格':<10}{'交易量':<10}{'盈亏金额':<12}{'收益率':<10}{'卖出原因':<12}")
        self.logger.info("-" * 100)
        
        for trade in sorted_trades[-10:][::-1]:  # TOP10盈利
            self.logger.info(
                f"{trade['ts_code']:<12}{trade['name']:<12}{trade['buy_date']:<12}"
                f"{trade['buy_price']:<10.2f}{trade['sell_date']:<12}{trade['sell_price']:<10.2f}"
                f"{trade['volume']:<10}{trade['profit_amount']:<12.2f}"
                f"{trade['profit_pct']*100:<10.2f}%{trade['reason']:<12}"
            )

        # 绘制收益曲线
        self.plot_returns()

def setup_logger():
    """设置日志系统"""
    config = Config()  # 创建配置对象以获取日志路径
    
    # 确保日志目录存在
    if not os.path.exists(config.log_path):
        os.makedirs(config.log_path)
        
    # 设置带时间戳的日志文件名
    timestamp = time.strftime('%Y%m%d_%H%M%S')
    log_file = os.path.join(config.log_path, f'backtest_{timestamp}.log')
    
    # 配置日志格式
    formatter = logging.Formatter(
        '%(asctime)s - %(levelname)s - %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    
    # 文件处理器
    file_handler = logging.FileHandler(log_file)
    file_handler.setFormatter(formatter)
    
    # 控制台处理器
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)
    
    # 配置根日志记录器
    root_logger = logging.getLogger()
    root_logger.setLevel(logging.INFO)
    
    # 移除现有的处理器（避免重复）
    for handler in root_logger.handlers[:]:
        root_logger.removeHandler(handler)
        
    # 添加新的处理器
    root_logger.addHandler(file_handler)
    root_logger.addHandler(console_handler)
    
    return root_logger

if __name__ == "__main__":
    # 加载配置
    config = Config()
    
    # 设置日志（传入配置对象）
    logger = setup_logger()
    timestamp = time.strftime("%Y%m%d_%H%M%S")
    logger.info(f"日志文件路径: {os.path.join(config.log_path, f'backtest_{timestamp}.log')}")
    
    # 获取股票列表
    data_loader = DataLoader(config)
    stock_list = data_loader.load_stock_list()
    
    # 初始化策略
    strategy = MyStrategy(config)
    
    # 初始化回测系统
    backtest_system = BacktestSystem(
        strategy=strategy,
        stock_list=stock_list,
        start_date=config.start_date,
        end_date=config.end_date,
        initial_capital=config.initial_capital,
        config=config
    )
    
    # 运行回测
    if backtest_system.run_backtest():
        # 分析和可视化结果
        analyzer = ResultAnalyzer(config)
        
        # 绘制收益率曲线
        analyzer.analyze_and_plot(
            backtest_system.strategy.daily_dates,
            backtest_system.strategy.daily_values,
            backtest_system.daily_position_counts,
            title='Strategy Performance'
        )
        
        # 绘制股票池规模图
        analyzer.plot_stock_pool_size(
            backtest_system.strategy.daily_stock_pool_sizes_dict
        )